"""
文件名: video_service.py
描述: 视频内容聚合和展示服务

本模块包含以下主要功能：
1. 视频数据的获取和缓存
2. 频道最新视频聚合
3. 分类视频筛选和展示
4. 视频搜索和排序
5. 视频统计和分析

依赖模块:
   - sqlalchemy: 数据库操作
   - datetime: 时间处理
   - typing: 类型注解
   - uuid: 唯一标识符

使用示例:
   >>> video_service = VideoService()
   >>> await video_service.sync_channel_videos(channel_id)
   >>> videos = await video_service.get_category_videos(category_id, user_id)
   >>> results = await video_service.search_videos(query, filters)

注意事项:
   - 视频数据缓存时间不宜过长
   - 批量同步要考虑API配额
   - 搜索功能要支持多种条件
   - 统计数据要实时更新
   - 要处理视频删除和失效

作者: AI助手
创建日期: 2024-06-27
最后修改: 2024-06-27
版本: 1.0.0
"""

import asyncio
import re
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple, Any
import uuid

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, update, delete, func, and_, or_, text
from sqlalchemy.orm import selectinload, joinedload

from src.core.database import get_db_session
from src.core.redis import redis_manager
from src.core.logger import get_logger
from src.models.video import (
    Video, 
    VideoCreate, 
    VideoUpdate, 
    VideoInDB,
    VideoSummary,
    VideoStats,
    VideoFilter,
)
from src.models.channel import Channel
from src.models.category import Category, channel_category_association
from src.services.youtube_service import YouTubeService
from src.utils.exceptions import (
    DatabaseException,
    ValidationException,
    YouTubeAPIException,
)

logger = get_logger(__name__)


class VideoService:
    """视频内容聚合和展示服务类"""
    
    def __init__(self):
        """初始化视频服务"""
        self.redis = redis_manager
        self.youtube_service = YouTubeService()
        
        # 缓存配置
        self.video_cache_ttl = 600  # 视频缓存10分钟
        self.search_cache_ttl = 300  # 搜索结果缓存5分钟
        
        # 同步配置
        self.max_videos_per_channel = 20  # 每个频道最多获取20个视频
        self.video_sync_interval = 3600  # 视频同步间隔1小时
    
    async def sync_channel_videos(
        self,
        channel_id: uuid.UUID,
        force_update: bool = False,
        max_videos: Optional[int] = None,
    ) -> Dict[str, Any]:
        """
        同步频道的最新视频
        
        Args:
            channel_id: 频道ID
            force_update: 是否强制更新
            max_videos: 最大视频数量
            
        Returns:
            Dict[str, Any]: 同步结果统计
        """
        try:
            async with get_db_session() as db:
                # 获取频道信息
                channel_stmt = select(Channel).where(
                    Channel.id == channel_id,
                    Channel.deleted_at.is_(None)
                )
                channel_result = await db.execute(channel_stmt)
                channel = channel_result.scalar_one_or_none()
                
                if not channel:
                    raise ValidationException("频道不存在")
                
                # 检查是否需要同步
                if not force_update and channel.last_sync_at:
                    time_since_sync = datetime.utcnow() - channel.last_sync_at
                    if time_since_sync.total_seconds() < self.video_sync_interval:
                        logger.debug(f"频道视频无需同步", channel_id=str(channel_id))
                        return {"status": "skipped", "reason": "too_recent"}
                
                # 设置最大视频数量
                max_videos = max_videos or self.max_videos_per_channel
                
                # 获取YouTube API视频数据
                published_after = None
                if channel.last_video_published_at:
                    # 获取上次同步后的视频
                    published_after = channel.last_video_published_at - timedelta(days=1)
                
                try:
                    youtube_videos = await self.youtube_service.get_channel_videos(
                        channel.channel_id,
                        max_results=max_videos,
                        published_after=published_after,
                        order="date"
                    )
                except YouTubeAPIException as e:
                    logger.error(f"获取YouTube视频失败: {e}", channel_id=str(channel_id))
                    return {"status": "failed", "error": str(e)}
                
                # 同步统计
                stats = {
                    "total_videos": len(youtube_videos),
                    "new_videos": 0,
                    "updated_videos": 0,
                    "failed_videos": 0,
                    "latest_published_at": channel.last_video_published_at,
                }
                
                # 处理每个视频
                for video_data in youtube_videos:
                    try:
                        video_result = await self._process_video_data(db, channel, video_data)
                        if video_result["action"] == "created":
                            stats["new_videos"] += 1
                        elif video_result["action"] == "updated":
                            stats["updated_videos"] += 1
                        
                        # 更新最新发布时间
                        published_at = video_result.get("published_at")
                        if published_at and (not stats["latest_published_at"] or published_at > stats["latest_published_at"]):
                            stats["latest_published_at"] = published_at
                            
                    except Exception as e:
                        logger.error(f"处理视频数据失败: {e}", video_data=video_data)
                        stats["failed_videos"] += 1
                        continue
                
                # 更新频道同步状态
                channel.last_sync_at = datetime.utcnow()
                if stats["latest_published_at"]:
                    channel.last_video_published_at = stats["latest_published_at"]
                channel.sync_status = "success"
                channel.sync_error = None
                channel.updated_at = datetime.utcnow()
                
                await db.commit()
                
                logger.info(f"频道视频同步完成", channel_id=str(channel_id), stats=stats)
                return {"status": "success", "stats": stats}
                
        except ValidationException:
            raise
        except Exception as e:
            logger.error(f"同步频道视频失败: {e}", channel_id=str(channel_id))
            
            # 更新频道同步错误状态
            try:
                async with get_db_session() as db:
                    channel_stmt = select(Channel).where(Channel.id == channel_id)
                    channel_result = await db.execute(channel_stmt)
                    channel = channel_result.scalar_one_or_none()
                    if channel:
                        channel.sync_status = "failed"
                        channel.sync_error = str(e)[:500]  # 限制错误信息长度
                        channel.updated_at = datetime.utcnow()
                        await db.commit()
            except Exception:
                pass
            
            raise DatabaseException(f"同步视频失败: {str(e)}")
    
    async def _process_video_data(
        self,
        db: AsyncSession,
        channel: Channel,
        video_data: Dict[str, Any]
    ) -> Dict[str, Any]:
        """
        处理单个视频数据
        
        Args:
            db: 数据库会话
            channel: 频道对象
            video_data: YouTube视频数据
            
        Returns:
            Dict[str, Any]: 处理结果
        """
        try:
            # 提取视频基本信息
            video_id = video_data.get("id", {}).get("videoId") or video_data.get("id")
            if not video_id:
                raise ValidationException("视频ID缺失")
            
            snippet = video_data.get("snippet", {})
            statistics = video_data.get("statistics", {})
            content_details = video_data.get("contentDetails", {})
            
            # 解析发布时间
            published_at = None
            if snippet.get("publishedAt"):
                try:
                    published_at = datetime.fromisoformat(
                        snippet["publishedAt"].replace("Z", "+00:00")
                    ).replace(tzinfo=None)
                except ValueError:
                    pass
            
            # 解析视频时长
            duration_seconds = None
            duration_iso = content_details.get("duration")
            if duration_iso:
                duration_seconds = self._parse_duration(duration_iso)
            
            # 检查视频是否已存在
            existing_stmt = select(Video).where(Video.video_id == video_id)
            existing_result = await db.execute(existing_stmt)
            existing_video = existing_result.scalar_one_or_none()
            
            if existing_video:
                # 更新现有视频
                update_data = VideoUpdate(
                    title=snippet.get("title", ""),
                    description=snippet.get("description", ""),
                    thumbnail_url=snippet.get("thumbnails", {}).get("medium", {}).get("url"),
                    duration=duration_iso,
                    duration_seconds=duration_seconds,
                    view_count=int(statistics.get("viewCount", 0)),
                    like_count=int(statistics.get("likeCount", 0)),
                    comment_count=int(statistics.get("commentCount", 0)),
                    tags=snippet.get("tags", []),
                    thumbnails=snippet.get("thumbnails", {}),
                    cached_at=datetime.utcnow(),
                    cache_expires_at=datetime.utcnow() + timedelta(seconds=self.video_cache_ttl),
                )
                
                for field, value in update_data.dict(exclude_unset=True).items():
                    if value is not None:
                        setattr(existing_video, field, value)
                
                existing_video.updated_at = datetime.utcnow()
                
                return {
                    "action": "updated",
                    "video_id": video_id,
                    "published_at": published_at,
                }
            
            else:
                # 创建新视频
                # 判断是否为短视频
                is_short = duration_seconds is not None and duration_seconds <= 60
                
                video_create = VideoCreate(
                    video_id=video_id,
                    channel_id=channel.id,
                    title=snippet.get("title", ""),
                    description=snippet.get("description", ""),
                    thumbnail_url=snippet.get("thumbnails", {}).get("medium", {}).get("url"),
                    duration=duration_iso,
                    duration_seconds=duration_seconds,
                    category_id=snippet.get("categoryId"),
                    view_count=int(statistics.get("viewCount", 0)),
                    like_count=int(statistics.get("likeCount", 0)),
                    comment_count=int(statistics.get("commentCount", 0)),
                    published_at=published_at,
                    tags=snippet.get("tags", []),
                    thumbnails=snippet.get("thumbnails", {}),
                    cache_expires_at=datetime.utcnow() + timedelta(seconds=self.video_cache_ttl),
                )
                
                video = Video(**video_create.dict())
                video.is_short = is_short
                video.cached_at = datetime.utcnow()
                video.created_at = datetime.utcnow()
                video.updated_at = datetime.utcnow()
                
                db.add(video)
                await db.flush()  # 获取ID
                
                return {
                    "action": "created",
                    "video_id": video_id,
                    "published_at": published_at,
                }
                
        except Exception as e:
            logger.error(f"处理视频数据失败: {e}", video_data=video_data)
            raise
    
    def _parse_duration(self, duration_iso: str) -> Optional[int]:
        """
        解析ISO 8601时长格式为秒数
        
        Args:
            duration_iso: ISO 8601格式的时长字符串 (如: PT4M13S)
            
        Returns:
            Optional[int]: 时长秒数，解析失败返回None
        """
        try:
            # 匹配 PT[数字H][数字M][数字S] 格式
            pattern = r'PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?'
            match = re.match(pattern, duration_iso)
            
            if not match:
                return None
            
            hours = int(match.group(1) or 0)
            minutes = int(match.group(2) or 0)
            seconds = int(match.group(3) or 0)
            
            return hours * 3600 + minutes * 60 + seconds
            
        except (ValueError, AttributeError):
            return None
    
    async def get_category_videos(
        self,
        category_id: uuid.UUID,
        user_id: uuid.UUID,
        limit: int = 50,
        offset: int = 0,
        filters: Optional[VideoFilter] = None,
    ) -> Tuple[List[VideoSummary], int]:
        """
        获取分类下的视频列表
        
        Args:
            category_id: 分类ID
            user_id: 用户ID
            limit: 每页数量
            offset: 偏移量
            filters: 筛选条件
            
        Returns:
            Tuple[List[VideoSummary], int]: (视频列表, 总数)
        """
        try:
            async with get_db_session() as db:
                # 验证分类权限
                category_stmt = select(Category).where(
                    Category.id == category_id,
                    Category.user_id == user_id,
                    Category.deleted_at.is_(None)
                )
                category_result = await db.execute(category_stmt)
                if not category_result.scalar_one_or_none():
                    raise ValidationException("分类不存在或无权限访问")
                
                # 构建基础查询
                base_query = (
                    select(Video)
                    .join(Channel, Video.channel_id == Channel.id)
                    .join(channel_category_association, Channel.id == channel_category_association.c.channel_id)
                    .where(
                        and_(
                            channel_category_association.c.category_id == category_id,
                            Channel.deleted_at.is_(None),
                            # 只显示未过期的缓存视频
                            or_(
                                Video.cache_expires_at.is_(None),
                                Video.cache_expires_at > datetime.utcnow()
                            )
                        )
                    )
                )
                
                # 应用筛选条件
                if filters:
                    base_query = self._apply_video_filters(base_query, filters)
                
                # 应用排序
                base_query = self._apply_video_sorting(base_query, filters)
                
                # 获取视频列表
                videos_query = base_query.limit(limit).offset(offset)
                videos_result = await db.execute(videos_query)
                videos = videos_result.scalars().all()
                
                # 获取总数
                count_query = select(func.count()).select_from(base_query.subquery())
                count_result = await db.execute(count_query)
                total_count = count_result.scalar()
                
                # 转换为摘要格式
                video_summaries = []
                for video in videos:
                    summary = VideoSummary(
                        id=video.id,
                        video_id=video.video_id,
                        title=video.title,
                        thumbnail_url=video.thumbnail_url,
                        duration_seconds=video.duration_seconds,
                        view_count=video.view_count,
                        published_at=video.published_at,
                        is_live=video.is_live,
                        is_short=video.is_short,
                    )
                    video_summaries.append(summary)
                
                return video_summaries, total_count
                
        except ValidationException:
            raise
        except Exception as e:
            logger.error(f"获取分类视频失败: {e}", category_id=str(category_id))
            raise DatabaseException(f"获取视频失败: {str(e)}")
    
    def _apply_video_filters(self, query, filters: VideoFilter):
        """应用视频筛选条件"""
        if not filters:
            return query
        
        # 搜索关键词
        if filters.search_query:
            search_term = f"%{filters.search_query}%"
            query = query.where(
                or_(
                    Video.title.ilike(search_term),
                    Video.description.ilike(search_term),
                    Channel.title.ilike(search_term)
                )
            )
        
        # 发布时间范围
        if filters.published_after:
            query = query.where(Video.published_at >= filters.published_after)
        if filters.published_before:
            query = query.where(Video.published_at <= filters.published_before)
        
        # 视频时长
        if filters.min_duration:
            query = query.where(Video.duration_seconds >= filters.min_duration)
        if filters.max_duration:
            query = query.where(Video.duration_seconds <= filters.max_duration)
        
        # 观看次数
        if filters.min_views:
            query = query.where(Video.view_count >= filters.min_views)
        
        # 隐私状态
        if filters.privacy_status:
            query = query.where(Video.privacy_status == filters.privacy_status)
        
        # 直播状态
        if filters.is_live is not None:
            query = query.where(Video.is_live == filters.is_live)
        
        # 短视频
        if filters.is_short is not None:
            query = query.where(Video.is_short == filters.is_short)
        
        # 标签筛选
        if filters.tags:
            # 使用PostgreSQL的JSONB操作符
            for tag in filters.tags:
                query = query.where(Video.tags.contains([tag]))
        
        return query
    
    def _apply_video_sorting(self, query, filters: Optional[VideoFilter]):
        """应用视频排序"""
        if not filters or not filters.sort_by:
            # 默认按发布时间降序
            return query.order_by(Video.published_at.desc())
        
        sort_column = None
        if filters.sort_by == "published_at":
            sort_column = Video.published_at
        elif filters.sort_by == "view_count":
            sort_column = Video.view_count
        elif filters.sort_by == "like_count":
            sort_column = Video.like_count
        elif filters.sort_by == "duration_seconds":
            sort_column = Video.duration_seconds
        elif filters.sort_by == "title":
            sort_column = Video.title
        
        if sort_column is not None:
            if filters.sort_order == "asc":
                query = query.order_by(sort_column.asc())
            else:
                query = query.order_by(sort_column.desc())
        
        return query
    
    async def search_videos(
        self,
        query: str,
        user_id: uuid.UUID,
        category_ids: Optional[List[uuid.UUID]] = None,
        limit: int = 50,
        offset: int = 0,
        filters: Optional[VideoFilter] = None,
    ) -> Tuple[List[VideoSummary], int]:
        """
        搜索用户的视频
        
        Args:
            query: 搜索关键词
            user_id: 用户ID
            category_ids: 限定分类ID列表（可选）
            limit: 每页数量
            offset: 偏移量
            filters: 附加筛选条件
            
        Returns:
            Tuple[List[VideoSummary], int]: (视频列表, 总数)
        """
        try:
            # 检查缓存
            cache_key = f"video_search:{user_id}:{hash(query)}:{hash(str(category_ids))}:{offset}:{limit}"
            cached_result = await self.redis.get(cache_key)
            if cached_result:
                return cached_result["videos"], cached_result["total"]
            
            async with get_db_session() as db:
                # 构建基础查询 - 获取用户可访问的视频
                base_query = (
                    select(Video)
                    .join(Channel, Video.channel_id == Channel.id)
                    .join(channel_category_association, Channel.id == channel_category_association.c.channel_id)
                    .join(Category, channel_category_association.c.category_id == Category.id)
                    .where(
                        and_(
                            Category.user_id == user_id,
                            Category.deleted_at.is_(None),
                            Channel.deleted_at.is_(None),
                            or_(
                                Video.cache_expires_at.is_(None),
                                Video.cache_expires_at > datetime.utcnow()
                            )
                        )
                    )
                )
                
                # 限定分类
                if category_ids:
                    base_query = base_query.where(Category.id.in_(category_ids))
                
                # 搜索关键词
                if query.strip():
                    search_term = f"%{query.strip()}%"
                    base_query = base_query.where(
                        or_(
                            Video.title.ilike(search_term),
                            Video.description.ilike(search_term),
                            Channel.title.ilike(search_term)
                        )
                    )
                
                # 应用附加筛选条件
                if filters:
                    base_query = self._apply_video_filters(base_query, filters)
                
                # 应用排序
                base_query = self._apply_video_sorting(base_query, filters)
                
                # 去重（因为视频可能属于多个分类）
                base_query = base_query.distinct()
                
                # 获取视频列表
                videos_query = base_query.limit(limit).offset(offset)
                videos_result = await db.execute(videos_query)
                videos = videos_result.scalars().all()
                
                # 获取总数
                count_query = select(func.count(func.distinct(Video.id))).select_from(
                    base_query.subquery()
                )
                count_result = await db.execute(count_query)
                total_count = count_result.scalar()
                
                # 转换为摘要格式
                video_summaries = []
                for video in videos:
                    summary = VideoSummary(
                        id=video.id,
                        video_id=video.video_id,
                        title=video.title,
                        thumbnail_url=video.thumbnail_url,
                        duration_seconds=video.duration_seconds,
                        view_count=video.view_count,
                        published_at=video.published_at,
                        is_live=video.is_live,
                        is_short=video.is_short,
                    )
                    video_summaries.append(summary)
                
                # 缓存结果
                result = {"videos": video_summaries, "total": total_count}
                await self.redis.set(cache_key, result, expire=self.search_cache_ttl)
                
                return video_summaries, total_count
                
        except Exception as e:
            logger.error(f"搜索视频失败: {e}", query=query, user_id=str(user_id))
            raise DatabaseException(f"搜索视频失败: {str(e)}")
    
    async def get_video_stats(
        self,
        user_id: uuid.UUID,
        category_id: Optional[uuid.UUID] = None,
    ) -> VideoStats:
        """
        获取视频统计信息
        
        Args:
            user_id: 用户ID
            category_id: 分类ID（可选）
            
        Returns:
            VideoStats: 视频统计信息
        """
        try:
            async with get_db_session() as db:
                # 构建基础查询
                base_query = (
                    select(Video)
                    .join(Channel, Video.channel_id == Channel.id)
                    .join(channel_category_association, Channel.id == channel_category_association.c.channel_id)
                    .join(Category, channel_category_association.c.category_id == Category.id)
                    .where(
                        and_(
                            Category.user_id == user_id,
                            Category.deleted_at.is_(None),
                            Channel.deleted_at.is_(None)
                        )
                    )
                )
                
                if category_id:
                    base_query = base_query.where(Category.id == category_id)
                
                # 获取统计数据
                stats_query = select(
                    func.count(Video.id).label("total_videos"),
                    func.sum(Video.view_count).label("total_views"),
                    func.sum(Video.like_count).label("total_likes"),
                    func.avg(Video.duration_seconds).label("avg_duration"),
                    func.max(Video.published_at).label("latest_video_at"),
                ).select_from(base_query.subquery())
                
                stats_result = await db.execute(stats_query)
                stats_row = stats_result.fetchone()
                
                stats = VideoStats(
                    total_videos=stats_row.total_videos or 0,
                    total_views=stats_row.total_views or 0,
                    total_likes=stats_row.total_likes or 0,
                    avg_duration=float(stats_row.avg_duration or 0),
                    latest_video_at=stats_row.latest_video_at,
                )
                
                return stats
                
        except Exception as e:
            logger.error(f"获取视频统计失败: {e}", user_id=str(user_id))
            raise DatabaseException(f"获取统计失败: {str(e)}")
    
    async def cleanup_expired_videos(self) -> int:
        """
        清理过期的视频缓存
        
        Returns:
            int: 清理的视频数量
        """
        try:
            async with get_db_session() as db:
                # 删除过期的视频缓存
                delete_stmt = delete(Video).where(
                    and_(
                        Video.cache_expires_at.is_not(None),
                        Video.cache_expires_at < datetime.utcnow()
                    )
                )
                
                result = await db.execute(delete_stmt)
                deleted_count = result.rowcount
                
                await db.commit()
                
                logger.info(f"清理过期视频缓存完成", deleted_count=deleted_count)
                return deleted_count
                
        except Exception as e:
            logger.error(f"清理过期视频缓存失败: {e}")
            return 0
    
    async def batch_sync_user_videos(
        self,
        user_id: uuid.UUID,
        max_channels: int = 10,
    ) -> Dict[str, Any]:
        """
        批量同步用户频道的视频
        
        Args:
            user_id: 用户ID
            max_channels: 最大同步频道数
            
        Returns:
            Dict[str, Any]: 同步结果统计
        """
        try:
            async with get_db_session() as db:
                # 获取需要同步的频道
                sync_threshold = datetime.utcnow() - timedelta(seconds=self.video_sync_interval)
                
                channels_stmt = (
                    select(Channel)
                    .join(channel_category_association)
                    .join(Category)
                    .where(
                        and_(
                            Category.user_id == user_id,
                            Category.deleted_at.is_(None),
                            Channel.deleted_at.is_(None),
                            Channel.is_active == True,
                            or_(
                                Channel.last_sync_at.is_(None),
                                Channel.last_sync_at < sync_threshold
                            )
                        )
                    )
                    .distinct()
                    .limit(max_channels)
                )
                
                channels_result = await db.execute(channels_stmt)
                channels = channels_result.scalars().all()
                
                if not channels:
                    return {"status": "no_channels", "total_channels": 0}
                
                # 批量同步统计
                batch_stats = {
                    "total_channels": len(channels),
                    "success_channels": 0,
                    "failed_channels": 0,
                    "total_new_videos": 0,
                    "total_updated_videos": 0,
                }
                
                # 并发同步（限制并发数避免API限制）
                semaphore = asyncio.Semaphore(3)  # 最多3个并发
                
                async def sync_single_channel(channel):
                    async with semaphore:
                        try:
                            result = await self.sync_channel_videos(channel.id)
                            if result["status"] == "success":
                                batch_stats["success_channels"] += 1
                                stats = result.get("stats", {})
                                batch_stats["total_new_videos"] += stats.get("new_videos", 0)
                                batch_stats["total_updated_videos"] += stats.get("updated_videos", 0)
                            else:
                                batch_stats["failed_channels"] += 1
                        except Exception as e:
                            logger.error(f"批量同步单个频道失败: {e}", channel_id=str(channel.id))
                            batch_stats["failed_channels"] += 1
                
                # 执行并发同步
                await asyncio.gather(*[sync_single_channel(channel) for channel in channels])
                
                logger.info(f"批量同步用户视频完成", user_id=str(user_id), stats=batch_stats)
                return {"status": "completed", "stats": batch_stats}
                
        except Exception as e:
            logger.error(f"批量同步用户视频失败: {e}", user_id=str(user_id))
            raise DatabaseException(f"批量同步失败: {str(e)}")


# 导出主要接口
__all__ = [
    "VideoService",
]